/**
 *  EKology - An Ecosystem Simulation Framework
 *  Copyright (C) 2008  Aarón Tavío - aaron.tavio at gmail.com - ULPGC
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package fungusEater.ekosystem;

import fungusEater.*;
import fungusEater.datatypes.*;

import net.ekology.ekosystem.*;
import net.ekology.ekosystem.datatypes.*;
import net.ekology.core.datatypes.*;

import java.util.Calendar;
import java.awt.Graphics2D;
import java.awt.geom.*;
import java.awt.*;


/**
 * @author Aarón Tavío - aaron.tavio at gmail.com
 * @version 1.0.0 - 20081019-1700
 */
public class Taros extends Biotope {
    // Attributes
    private Rectangle2D.Double oRectangle;
    private Factory oFactory;
    
    
    ///////////////////////////////////
    // Initialization methods
    ///////////////////////////////////
    @Override
    public void initialize() {
        double[] aDimension = {10000.0,10000.0}; // IMPRESCINDIBLE
        
        setID(FungusEaterHelper.generateID());
        setSpace(new Space(aDimension));
        
        oRectangle = new Rectangle2D.Double(-getSpace().getDimension(0)/2, -getSpace().getDimension(1)/2, getSpace().getDimension(0), getSpace().getDimension(1));
        oFactory = getFactory();        
    }
    

    @Override
    public void preprocess() {
        cleanTaros();
    }
    
    
    
    
    ///////////////////////////////////
    // Control methods
    ///////////////////////////////////
    public void checkState(String sAgentID, String sCurrentState) {
        Agent oAgent;
        
        oAgent = oEKosystem.getAgent(sAgentID);
        
        if (oAgent instanceof Fungus) checkState((Fungus) oAgent, sCurrentState);
        else if (oAgent instanceof FungusEater) checkState((FungusEater) oAgent, sCurrentState);
        else if (oAgent instanceof Factory) checkState((Factory) oAgent, sCurrentState);
    }
    
    
    private void checkState(Fungus oFungus, String sCurrentState) {
        if (sCurrentState.equals("juniorResting")) {
            if ((int) oFungus.getAge(Calendar.SECOND) > 12) oEKosystem.addEvent(new EKEvent("IS_SENIOR",this.getID(),oFungus.getID(),null));
            else if (oFungus.getEnergy() > 0.8) oEKosystem.addEvent(new EKEvent("ENOUGH_ENERGY",this.getID(),oFungus.getID(),null));
            else if (oFungus.getEnergy() < 0.6) oEKosystem.addEvent(new EKEvent("NEED_ENERGY",this.getID(),oFungus.getID(),null));
        }
        else if (sCurrentState.equals("growing")) {
            if (oFungus.getEnergy() < 0.3) oEKosystem.addEvent(new EKEvent("NEED_ENERGY",this.getID(),oFungus.getID(),null));
        }
        else if (sCurrentState.equals("juniorSynthesizing")) {
            if (oFungus.getEnergy() > 0.9) oEKosystem.addEvent(new EKEvent("ENOUGH_ENERGY",this.getID(),oFungus.getID(),null));
        }
        else if (sCurrentState.equals("seniorResting")) {
            if ((int) oFungus.getAge(Calendar.SECOND) > 240) oEKosystem.addEvent(new EKEvent("IS_TOO_OLD",this.getID(),oFungus.getID(),null));
            else if (oFungus.getEnergy() > 0.8) oEKosystem.addEvent(new EKEvent("ENOUGH_ENERGY",this.getID(),oFungus.getID(),null));
            else if (oFungus.getEnergy() < 0.6) oEKosystem.addEvent(new EKEvent("NEED_ENERGY",this.getID(),oFungus.getID(),null));
        }
        else if (sCurrentState.equals("breeding")) {
            if (oFungus.getEnergy() < 0.7) oEKosystem.addEvent(new EKEvent("NEED_ENERGY",this.getID(),oFungus.getID(),null));
        }
        else if (sCurrentState.equals("seniorSynthesizing")) {
            if (oFungus.getEnergy() > 0.9) oEKosystem.addEvent(new EKEvent("ENOUGH_ENERGY",this.getID(),oFungus.getID(),null));
        }
    }
    
    
    private void checkState(FungusEater oFungusEater, String sCurrentState) {
        if (sCurrentState.equals("seekingFungus")) {
            if (oFungusEater.fungusFound()) oEKosystem.addEvent(new EKEvent("FUNGUS_FOUND",this.getID(),oFungusEater.getID(),null));
        }
        else if (sCurrentState.equals("eating")) {
            if (oFungusEater.getEnergy() > 0.8) oEKosystem.addEvent(new EKEvent("ENOUGH_FOOD",this.getID(),oFungusEater.getID(),null));
            else if (!oFungusEater.fungusFound()) oEKosystem.addEvent(new EKEvent("NEED_FOOD",this.getID(),oFungusEater.getID(),null));
        }
        else if (sCurrentState.equals("seekingOre")) {
            if (oFungusEater.getEnergy() < 0.25) oEKosystem.addEvent(new EKEvent("NEED_FOOD",this.getID(),oFungusEater.getID(),null));
            else if (oFungusEater.oreFound()) oEKosystem.addEvent(new EKEvent("ORE_FOUND",this.getID(),oFungusEater.getID(),null));
        }
        else if (sCurrentState.equals("collectingOre")) {
            if (!oFungusEater.oreFound()) oEKosystem.addEvent(new EKEvent("NO_ORE_HERE",this.getID(),oFungusEater.getID(),null));
            else if (oFungusEater.fullLoaded() || oFungusEater.getEnergy() < 0.3) oEKosystem.addEvent(new EKEvent("SEEK_FACTORY",this.getID(),oFungusEater.getID(),null));
        }
        else if (sCurrentState.equals("seekingFactory")) {
            if (oFungusEater.factoryFound()) oEKosystem.addEvent(new EKEvent("FACTORY_FOUND",this.getID(),oFungusEater.getID(),null));
        }
        else if (sCurrentState.equals("unloadingOre")) {
            if (oFungusEater.empty() && oFungusEater.getEnergy() < 0.3) oEKosystem.addEvent(new EKEvent("NEED_FOOD",this.getID(),oFungusEater.getID(),null));
            else if (oFungusEater.empty() && oFungusEater.getEnergy() >= 0.3) oEKosystem.addEvent(new EKEvent("FOOD_ENOUGH",this.getID(),oFungusEater.getID(),null));
        }

    }
    
    
    private void checkState(Factory oFactory, String sCurrentState) {
        if (sCurrentState.equals("incompleteColonization")) {
            if (getFungusEaterCount() < oFactory.getMinCommunitySize()) oEKosystem.addEvent(new EKEvent("FEW_COLLECTORS",this.getID(),oFactory.getID(),null));
            else if (oFactory.enoughOre()) oEKosystem.addEvent(new EKEvent("ENOUGH_ORE",this.getID(),oFactory.getID(),null));
        }
        else if (sCurrentState.equals("fewCollectors")) {
            if (oFactory.enoughOre()) oEKosystem.addEvent(new EKEvent("ENOUGH_ORE",this.getID(),oFactory.getID(),null));
            else if (oFactory.enoughOreToBuild()) oEKosystem.addEvent(new EKEvent("ENOUGH_ORE_TO_BUILD",this.getID(),oFactory.getID(),null));
        }
        else if (sCurrentState.equals("buildingCollector")) {
            if (oFactory.enoughOre()) oEKosystem.addEvent(new EKEvent("ENOUGH_ORE",this.getID(),oFactory.getID(),null));
            else if (getFungusEaterCount() >= oFactory.getMinCommunitySize()) oEKosystem.addEvent(new EKEvent("ENOUGH_COLLECTORS",this.getID(),oFactory.getID(),null));
        }
    }
    
    
    @Override
    public String[] getSignalNames() {
        String[] aResult = {"FUNGUS_EATER_COUNT","TOTAL_BIOMASS", "TOTAL_ORE"};
        
        return aResult;
    }
    
    @Override
    public SignalData getSignalData(String sSignal) {
        SignalData oResult;
        
        if (sSignal.equals("FUNGUS_EATER_COUNT")) oResult = new SignalData(sSignal,SignalType.INTEGER);
        else if (sSignal.equals("TOTAL_BIOMASS")) oResult = new SignalData(sSignal,SignalType.DOUBLE);
        else if (sSignal.equals("TOTAL_ORE")) oResult = new SignalData(sSignal,SignalType.DOUBLE);
        else oResult = null;
        
        return oResult;
    }
    
    @Override
    public ValueData getValueData(String sSignal) {
        ValueData oResult;
        
        if (sSignal.equals("FUNGUS_EATER_COUNT")) oResult = new ValueData(sSignal,getLocalDate(),getFungusEaterCount());
        else if (sSignal.equals("TOTAL_BIOMASS")) oResult = new ValueData(sSignal,getLocalDate(),getTotalBiomass());
        else if (sSignal.equals("TOTAL_ORE")) oResult = new ValueData(sSignal,getLocalDate(),getTotalOre());
        else oResult = null;
        
        return oResult;
    }
    
    
    
    ///////////////////////////////////
    // Agent comunication methods
    ///////////////////////////////////
    public int goTo(String sAgentID, EKVector oDestination, int iEKTimeSlice) {
        EKVector oAgentRotationVector;
        double dAgentRotationAngle;
        int iEKTimeUsed;
        
        oAgentRotationVector = (EKVector) oEKosystem.getAgent(sAgentID).getOrientation();
        dAgentRotationAngle = oAgentRotationVector.getRotationAngle() - oDestination.getRotationAngle();
        if (Math.abs(dAgentRotationAngle) > Config.PROXIMITY_THRESHOLD) {
            iEKTimeUsed = headTo(sAgentID, oDestination, iEKTimeSlice);
            
        }
        else iEKTimeUsed = translateTo(sAgentID, oDestination, iEKTimeSlice);
        
        return iEKTimeUsed;
    }
    
    
    private int headTo(String sAgentID, EKVector oDirection, int iEKTimeSlice) {
        EKVector oAgentRotation;
        double dMaxRotation,dRotationIncrement;
        int iEKTimeUsed;
        
        oAgentRotation = (EKVector) oEKosystem.getAgent(sAgentID).getOrientation();
        
        dRotationIncrement = oDirection.getRotationAngle() - oAgentRotation.getRotationAngle();
        if (dRotationIncrement != 0) {
            dMaxRotation = iEKTimeSlice*Config.FE_ROTATION_SPEED;
            if (dMaxRotation > Math.abs(dRotationIncrement)) {
                oAgentRotation.setRotationAngle(oDirection.getRotationAngle());
                iEKTimeUsed = (int) Math.ceil(Math.abs(dRotationIncrement)/Config.FE_ROTATION_SPEED);
            }
            else {
                if (dRotationIncrement > 0) oAgentRotation.setRotationAngle(oAgentRotation.getRotationAngle()+dMaxRotation);
                else oAgentRotation.setRotationAngle(oAgentRotation.getRotationAngle()-dMaxRotation);
                iEKTimeUsed = iEKTimeSlice;
            }
        } else iEKTimeUsed = 0;
        
        oEKosystem.getAgent(sAgentID).setOrientation(oAgentRotation);
        
        return iEKTimeUsed;
    }
    
    
    private int translateTo(String sAgentID, EKVector oDestination, int iEKTimeSlice) {
        EKVector oNewPosition;
        double dMaxDistance;
        double dDistance;
        Agent oAgent;
        int iEKTimeUsed;
        
        if (oDestination.length() > 0) {
            dMaxDistance = iEKTimeSlice*Config.FE_MOVEMENT_SPEED;
        
            if (dMaxDistance>oDestination.length()) {
                dDistance = oDestination.length();
                iEKTimeUsed = (int) Math.ceil(dDistance/Config.FE_MOVEMENT_SPEED);
            }
            else {
                dDistance = dMaxDistance;
                iEKTimeUsed = iEKTimeSlice;
            }

            oNewPosition = new EKVector(oDestination,dDistance);
            oAgent = oEKosystem.getAgent(sAgentID);

            oNewPosition.add((EKVector) oAgent.getPosition());
            oAgent.setPosition(oNewPosition);
        }
        else iEKTimeUsed = 0;
        
        return iEKTimeUsed;
    }
    
    
    /**
     * Devuelve la posición del hogo más cercano al comedor de hongos
     * 
     * @param sCollectorID
     * @param oFungusType
     * @return
     */
    public String sniff(String sCollectorID, FungusType oFungusType) {
        String sResult = null;
        double dDistance = Double.MAX_VALUE;
        EKVector oFungusPosition;
        FungusEater oFungusEater;
        Fungus oFungus;
        
        oFungusEater = (FungusEater) oEKosystem.getAgent(sCollectorID);
        
        for (Agent oAgent: oEKosystem.getAgents()) {
            if (oAgent instanceof Fungus) {
                oFungus = (Fungus) oAgent;
                if (oFungus.getType() == oFungusType && oFungus.getWeight() > 0) {
                    oFungusPosition = getRelativeVector(oFungusEater.getPosition(),oFungus.getPosition());
                    if (sResult == null || oFungusPosition.length() < dDistance) {
                        sResult = oFungus.getID();
                        dDistance = oFungusPosition.length();
                    }

                }
            }
        }
        
        return sResult;
    }
    
    
    public String scanForOre(String sCollectorID, OreType oOreType) {
        String sResult = null;
        double dDistance = Double.MAX_VALUE;
        EKVector oOrePosition;
        FungusEater oFungusEater;
        Ore oOre;
        
        oFungusEater = (FungusEater) oEKosystem.getAgent(sCollectorID);
        
        for (Agent oAgent: oEKosystem.getAgents()) {
            if (oAgent instanceof Ore) {
                oOre = (Ore) oAgent;
                if (oOre.getType() == oOreType && oOre.getWeight() > 0) {
                    oOrePosition = getRelativeVector(oFungusEater.getPosition(),oOre.getPosition());
                    if (sResult == null || oOrePosition.length() < dDistance) {
                        sResult = oOre.getID();
                        dDistance = oOrePosition.length();
                    }

                }
            }
        }
        
        return sResult;
    }
    
    
    private void cleanTaros() {
        Fungus oFungus;
        FungusEater oFungusEater;
        Ore oOre;
        
        System.out.printf("Taros.java->cleanTaros();\n"); // DEBUG
        for(Agent oAgent: oEKosystem.getAgents()) {
            if (oAgent instanceof Fungus) {
                oFungus = (Fungus) oAgent;
                if (oFungus.getEnergy() <= 0 || oFungus.getWeight() <= 0) {
                    System.out.printf("Agent:%s; killed\n",oAgent.getID());
                    oEKosystem.removeAgent(oFungus.getID());
                }
            }
            else if (oAgent instanceof FungusEater) {
                oFungusEater = (FungusEater) oAgent;
                if (oFungusEater.getEnergy() <= 0) {
                    System.out.printf("Agent:%s; killed\n",oAgent.getID());
                    oFungusEater.kill();
                    oEKosystem.removeAgent(oFungusEater.getID());
                }
            }
            else if (oAgent instanceof Ore) {
                oOre = (Ore) oAgent;
                if (oOre.getWeight() <= 0) {
                    System.out.printf("Agent:%s; killed\n",oAgent.getID());
                    oEKosystem.removeAgent(oOre.getID());
                }
            }
        }
    }
    
    
    public double eat(String sFungusEaterID, String sFungusID) {
        double dResult = 0;
        Fungus oFungus;
        FungusEater oFungusEater;
        
        oFungus = (Fungus) oEKosystem.getAgent(sFungusID);
        oFungusEater = (FungusEater) oEKosystem.getAgent(sFungusEaterID);
        
        if (oFungus != null && oFungusEater != null) {
            if (getRelativeVector(oFungusEater.getPosition(),oFungus.getPosition()).length() < Config.PROXIMITY_THRESHOLD) dResult = oFungus.incomingBite();
        }

        return dResult;
    }
    
    public double loadOre(String sFungusEaterID, String sOreID, double dAmount) {
        double dResult = 0;
        Ore oOre;
        FungusEater oFungusEater;
        
        oOre = (Ore) oEKosystem.getAgent(sOreID);
        if (oOre != null) {
            oFungusEater = (FungusEater) oEKosystem.getAgent(sFungusEaterID);
        
            if (getRelativeVector(oFungusEater.getPosition(),oOre.getPosition()).length() < Config.PROXIMITY_THRESHOLD)
                dResult = oOre.loadOre(dAmount);
        }
        
        return dResult;
    }
    
    
    public void unloadOre(String sFungusEaterID, OreType oType, double dAmount) {
        FungusEater oFungusEater;
        
        oFungusEater = (FungusEater) oEKosystem.getAgent(sFungusEaterID);
        
        if (getRelativeVector(oFungusEater.getPosition(),oFactory.getPosition()).length() < Config.PROXIMITY_THRESHOLD) oFactory.unloadOre(oFungusEater, oType, dAmount);
    }
    
    
    public EKVector radioBeacon(String sFungusEater) {
        if (oFactory == null) oFactory = getFactory();
        
        return getRelativeVector(oEKosystem.getAgent(sFungusEater).getPosition(),oFactory.getPosition());
    }
    
    
    public EKVector getAgentRelativePosition(String sAgentID, EKVector oRelativeTo) {
        EKVector oResult = null;
        
        oResult = getRelativeVector(oRelativeTo,oEKosystem.getAgent(sAgentID).getPosition());
        
        return oResult;
    }
    
    
    private EKVector getRelativeVector(EKVector oSource, EKVector oDestination) {
        EKVector oResult;
        
        oResult = oDestination.clone();
        oResult.sub(oSource);
        
        return oResult;
    }
    
    private Factory getFactory() {
        Factory oResult = null;
        
        if (oFactory != null) oResult = oFactory;
        else {
            for (Agent oAgent: oEKosystem.getAgents()) {
                if (oAgent instanceof Factory) {
                    oResult = (Factory) oAgent;
                    break;
                }
            }
        }
        
        return oResult;
    }
    
    
    
    ///////////////////////////////////
    // Other methods
    ///////////////////////////////////
    @Override
    public void draw(Graphics2D g2) {
        g2.setStroke(new BasicStroke(2f));
        g2.setColor(new Color(0.7f,0f,0f));
        g2.draw(oRectangle);
        
        super.draw(g2);
    }
    
    
    private int getFungusEaterCount() {
        int iResult;
        
        iResult = 0;
        for(Agent oAgent: oEKosystem.getAgents()) {
            if (oAgent instanceof FungusEater) iResult++;
        }
        
        return iResult;
    }
    
    public void fungusEaterCrated(FungusEater oFungusEater) {
        oFungusEater.setPosition(getFactory().getPosition());
        oFungusEater.setOrientation(getFactory().getOrientation());
        oEKosystem.addAgent(oFungusEater);
    }
    
    
    public void fungusBorn(Fungus oParent, Fungus oFungus) {
        double dXDisp,dYDisp;
        double dHalfDispersion;
        
        dHalfDispersion = Config.F_DISPERSION_FACTOR/2;
        dXDisp = FungusEaterHelper.getRandomValue(-dHalfDispersion, dHalfDispersion) ;
        dYDisp = FungusEaterHelper.getRandomValue(-dHalfDispersion, dHalfDispersion) ;
        
        oFungus.setPosition(oParent.getPosition().add(new EKVector(dXDisp,dYDisp)));
        oFungus.setOrientation(oParent.getOrientation());

        oEKosystem.addAgent(oFungus);
    }
    
    public double getTotalBiomass() {
        double dResult;
        
        dResult = 0;
        for(Agent oAgent: oEKosystem.getAgents()) {
            if (oAgent instanceof Fungus) dResult += Fungus.class.cast(oAgent).getWeight();
        }
        
        return dResult;
    }
    
    public double getTotalOre() {
        double dResult;
        
        dResult = 0;
        for(Agent oAgent: oEKosystem.getAgents()) {
            if (oAgent instanceof Ore) dResult += Ore.class.cast(oAgent).getWeight();
        }
        
        return dResult;
    }
    
    
}
